﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.Caching;
using System.Runtime.Caching.Configuration;
using Enyim.Caching;
using Enyim.Caching.Configuration;
using System.Net;

namespace Catalyst.Caching
{
	public class Memcached : ObjectCache
	{
		static Dictionary<string, MemcachedClient> _caches;

		public Memcached() : this((MemcachedSection)ConfigurationManager.GetSection("system.runtime.caching/memcached"))
		{
		}

		public Memcached(MemcachedSection section)
		{
			if (_caches == null)
			{
				Initialize(section);
			}
		}

		public static void Initialize(MemcachedSection section)
		{
			_caches = new Dictionary<string, MemcachedClient>();

			string zone = null;

			foreach (SocketPoolElement element in section.SocketPools)
			{
				if (String.IsNullOrEmpty(element.Name))
				{
					throw new Exception("Name not specified.");
				}

				zone = element.Name;

				CatalystMemcachedClientConfiguration config = new CatalystMemcachedClientConfiguration();
				
				config.Protocol = Enyim.Caching.Memcached.MemcachedProtocol.Binary;
				config.Authentication.Type = typeof(Enyim.Caching.Memcached.PlainTextAuthenticator);
				
				if (String.IsNullOrEmpty(element.UserName))
				{
					throw new Exception("UserName not specified.");
				}

				if (String.IsNullOrEmpty(element.Password))
				{
					throw new Exception("Password not specified.");
				}

				config.Authentication.Parameters["zone"] = zone;
				config.Authentication.Parameters["userName"] = element.UserName;
				config.Authentication.Parameters["password"] = element.Password;

				config.PerformanceMonitor = new Enyim.Caching.Memcached.DefaultPerformanceMonitor(zone);

				foreach (ServerElement server in element.Servers)
				{
					config.Servers.Add(new IPEndPoint(IPAddress.Parse(server.Address), server.Port));
				};

				MemcachedClient client = new MemcachedClient(config);

				_caches.Add(zone, client);
			}
		}

		internal MemcachedClient GetCache(string regionName = null)
		{
			if (regionName == null)
			{
				throw new Exception("Must specify a regionName.");
			}

			return _caches[regionName];
		}

		public override bool Add(CacheItem item, CacheItemPolicy policy)
		{
			return Add(item.Key, item.Value, policy, item.RegionName);
		}

		public virtual bool Add(string key, object value, string regionName = null)
		{
			return Add(key, value, ObjectCache.InfiniteAbsoluteExpiration, regionName);
		}

		public override bool Add(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			return Add(key, value, policy.AbsoluteExpiration, regionName);
		}

		public override bool Add(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			MemcachedClient cache = GetCache(regionName);
			return cache.Store(Enyim.Caching.Memcached.StoreMode.Add, key, value, absoluteExpiration.DateTime);
		}

		public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy)
		{
			return new CacheItem(value.Key, AddOrGetExisting(value.Key, value.Value, policy, value.RegionName));
		}

		public virtual object AddOrGetExisting(string key, object value, string regionName = null)
		{
			return AddOrGetExisting(key, value, ObjectCache.InfiniteAbsoluteExpiration);
		}

		public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			return AddOrGetExisting(new CacheItem(key, value), policy).Value;
		}

		public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			if (!Contains(key))
			{
				Add(key, value, absoluteExpiration);
			}

			return Get(key);
		}

		/// <summary>
		/// Retrieves actual cache item to determine if contains by key (inefficient)
		/// </summary>
		/// <param name="key"></param>
		/// <param name="regionName"></param>
		/// <returns></returns>
		public override bool Contains(string key, string regionName = null)
		{
			object obj = Get(key, regionName);

			if (obj != null)
			{
				return true;
			}

			return false;
		}

		public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
		{
			throw new NotImplementedException();
		}

		public override DefaultCacheCapabilities DefaultCacheCapabilities
		{
			get
			{
				return DefaultCacheCapabilities.AbsoluteExpirations
					| DefaultCacheCapabilities.SlidingExpirations 
					| DefaultCacheCapabilities.OutOfProcessProvider 
					| DefaultCacheCapabilities.CacheRegions 
					| DefaultCacheCapabilities.CacheEntryUpdateCallback;
			}
		}

		public override object Get(string key, string regionName = null)
		{
			MemcachedClient cache = GetCache(regionName);
			return cache.Get(key);
		}

		public override CacheItem GetCacheItem(string key, string regionName = null)
		{
			return new CacheItem(key, Get(key));
		}

		public override long GetCount(string regionName = null)
		{
			throw new NotImplementedException();
		}

		protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
		{
			throw new NotImplementedException();
		}

		public override IDictionary<string, object> GetValues(string regionName, params string[] keys)
		{
			return GetValues(keys, regionName);
		}

		public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
		{
		   return GetCache(regionName).Get(keys);
		}

		public override string Name
		{
			get { return "Memcached"; }
		}

		public override object Remove(string key, string regionName = null)
		{
			GetCache(regionName).Remove(key);

			return null;
		}

		public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			Set(key, value, policy.AbsoluteExpiration, regionName);
		}

		public override void Set(CacheItem item, CacheItemPolicy policy)
		{
			Set(item.Key, item.Value, policy.AbsoluteExpiration, item.RegionName);
		}

		public virtual void Set(string key, object value, string regionName = null)
		{
			Set(key, value, ObjectCache.InfiniteAbsoluteExpiration, regionName);
		}

		public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			MemcachedClient cache = GetCache(regionName);
			cache.Store(Enyim.Caching.Memcached.StoreMode.Set, key, value, absoluteExpiration.DateTime);
		}

		public override object this[string key]
		{
			get
			{
				return GetCache().Get(key);
			}
			set
			{
				GetCache().Store(Enyim.Caching.Memcached.StoreMode.Set, key, value);
			}
		}
	}
}
